--[[

    Filename:	auxcoap.lua

    Programmer:	aesiot

    Remark:

--]]

local CoAP   = require( "CoAP"   )
local Crypto = require( "Crypto" )
local auxlib = require( "auxlib" )
local auxtmr = require( "auxtmr" )

local auxcoap = { _VERSION = "1.0" }

--------------------------------------------------------------------------------

local function gen_token( addr, ident )

	return string.pack( ">j", Crypto.fnv_hash( tostring( addr ) .. tostring( ident ) .. tostring( os.get_tick_count() ) ) )
end

local function hash_uri_path( opts )

	if auxlib.is_string( opts ) then

		local splice = ""

		for w in string.gmatch( opts, "[^\\/]+" ) do

			splice = splice .. w
		end

		return Crypto.fnv_hash( splice )

	elseif auxlib.is_table( opts ) then

		local item = opts[ CoAP.OPTION_URI_PATH ]

		if auxlib.is_string( item ) then

			return Crypto.fnv_hash( item )
		else

			local splice = ""

			auxlib.array_for_each( item, function( v )

				splice = splice .. v
			end )

			return Crypto.fnv_hash( splice )
		end
	end

	return nil
end

--------------------------------------------------------------------------------

local fmtmap = {

	[ "TEXT_PLAIN"   ] = CoAP.MEDIATYPE_TEXT_PLAIN,
	[ "LINK_FORMAT"  ] = CoAP.MEDIATYPE_APPLICATION_LINK_FORMAT,
	[ "XML"          ] = CoAP.MEDIATYPE_APPLICATION_XML,
	[ "OCTET_STREAM" ] = CoAP.MEDIATYPE_APPLICATION_OCTET_STREAM,
	[ "RDF_XML"      ] = CoAP.MEDIATYPE_APPLICATION_RDF_XML,
	[ "EXI"          ] = CoAP.MEDIATYPE_APPLICATION_EXI,
	[ "JSON"         ] = CoAP.MEDIATYPE_APPLICATION_JSON
}

local optmap = {

	[ "uri-host" ] = { CoAP.OPTION_URI_HOST },
	[ "observe"  ] = { CoAP.OPTION_OBSERVE  },
	[ "uri-port" ] = { CoAP.OPTION_URI_PORT },

	[ "uri-path" ] = { CoAP.OPTION_URI_PATH, function( value )

		if auxlib.is_string( value ) then

			local list = {}

			for word in string.gmatch( value, "[^\\/]+" ) do

				table.insert( list, word )
			end

			return ( #list > 1 and list ) or list[ 1 ]
		else

			return value
		end
	end },

	[ "content-format" ] = { CoAP.OPTION_CONTENT_FORMAT, function( value )

		return auxlib.assert( fmtmap[ string.upper( value ) ], "invalid value" )
	end },

	[ "uri-query" ] = { CoAP.OPTION_URI_QUERY, function( value )

		if auxlib.is_string( value ) then

			local list = {}

			for word in string.gmatch( value, "[^&]+" ) do

				table.insert( list, word )
			end

			return ( #list > 1 and list ) or list[ 1 ]
		else

			return value
		end
	end },

	[ "block1" ] = { CoAP.OPTION_BLOCK1, function( value )

		if auxlib.is_table( value ) then

			return CoAP.gen_option_block( value.num, value.more, value.szx )
		else

			return value
		end
	end },

	[ "block2" ] = { CoAP.OPTION_BLOCK2, function( value )

		if auxlib.is_table( value ) then

			return CoAP.gen_option_block( value.num, value.more, value.szx )
		else

			return value
		end
	end },

	[ "size1" ] = { CoAP.OPTION_SIZE1 },
	[ "size2" ] = { CoAP.OPTION_SIZE2 }
}

function auxcoap.genopts( ... )

	local options = {}

	local metatable = {}

	metatable.__index = metatable

	function metatable:__newindex( index, value )

		auxlib.assert( auxlib.is_integer( index ) or auxlib.is_string( index ), "invalid index" )

		if auxlib.is_string( index ) then

			local item = auxlib.assert( optmap[ string.lower( index ) ], "invalid index" )

			rawset( self, item[ 1 ], ( ( item[ 2 ] and item[ 2 ]( value ) ) or value ) )
		else

			rawset( self, index, value )
		end

		return self
	end

	function metatable:set( index, value )

		self[ index ] = value

		return self
	end

	setmetatable( options, metatable )

	auxlib.varargs_pairs( function( index, value ) options[ index ] = value end, ... )

	return options
end

--------------------------------------------------------------------------------

function auxcoap.get_uri_path( opts )

	local opt = ( ( opts and opts[ CoAP.OPTION_URI_PATH ] ) or '' )

	if auxlib.is_string( opt ) then

		return opt

	elseif auxlib.is_table( opt ) then

		return table.concat( opt, '/' )
	else

		return ''
	end
end

function auxcoap.is_observe( opts )

	local obs = auxlib.is_table( opts ) and opts[ CoAP.OPTION_OBSERVE ]

	return null == obs or ( auxlib.is_integer( obs ) and 0 == obs )
end

function auxcoap.is_ping_message( type, code, token )

	return CoAP.CON == type and CoAP.RESPONSE_EMPTY == code and nil == token
end

--------------------------------------------------------------------------------

local function new_stm_client( object )

	local result = {}

	local metatable = { Ident = 0, Object = object, Observer = {}, URITable = {},

		__index = {},

		__metatable = "cannot get a protected metatable"
	}

	metatable.Registry = auxtmr.create_timeout_container( 1000, 5000, function( ackfn )

		auxlib.pcall( ackfn, result )
	end )

	function object.on_close()

		metatable.Registry.clear( true )

		metatable.Registry.close()

		auxlib.pcall( result.on_close, result )

		metatable.Object.on_close    = nil
		metatable.Object.on_ready    = nil
		metatable.Object.on_request  = nil
		metatable.Object.on_response = nil

		metatable.Ident    = nil
		metatable.Object   = nil
		metatable.Observer = nil
		metatable.URITable = nil
		metatable.Registry = nil

		metatable = nil
	end

	function object.on_destroy()

		auxlib.pcall( result.on_destroy, result )

		result = nil
	end

	function object.on_ready()

		auxlib.pcall( result.on_ready, result )
	end

	function object.on_request( code, token, options, payload )

		local proc = metatable.URITable[ hash_uri_path( options ) ]

		auxlib.pcall( ( proc or result.on_request ), result, code, token, options, payload )
	end

	function object.on_response( code, token, options, payload )

		local proc = metatable.Observer[ token ] or metatable.Registry.remove( token )

		auxlib.pcall( ( proc or result.on_response ), result, code, token, options, payload )
	end

	function metatable:gen_ident()

		local ident = self.Ident + 1

		self.Ident = ident

		return ident
	end

	function metatable.__index:close()

		auxlib.assert( self == result )

		auxlib.assert( metatable.Object:close() )

		return self
	end

	function metatable.__index:request( code, options, payload, ackfn, timeout )

		auxlib.assert( self == result )

		auxlib.argcheck( auxlib.is_integer( code ), 2, "(integer expected)" )

		auxlib.argcheck( code < 32, 2, "(invalid value)" )

		local token = gen_token( self, metatable:gen_ident() )

		auxlib.assert( metatable.Object:send( code, token, options, payload ) )

		auxlib.assert( metatable.Registry.insert( token, ackfn, timeout ), "unable to register session" )

		return self
	end

	function metatable.__index:response( code, token, options, payload )

		auxlib.assert( self == result )

		auxlib.argcheck( auxlib.is_integer( code ), 2, "(integer expected)" )

		auxlib.argcheck( code > 31, 2, "(invalid value)" )

		auxlib.assert( metatable.Object:send( code, token, options, payload ) )

		return self
	end

	function metatable.__index:bind( path, proc )

		auxlib.assert( self == result )

		auxlib.argcheck( auxlib.is_string( path ), 2, "(string expected)" )

		auxlib.argcheck( auxlib.is_function( proc ) or auxlib.is_nil( proc ), 3, "(function expected)" )

		metatable.URITable[ hash_uri_path( path ) ] = proc

		return self
	end

	function metatable.__index:observe( token, proc )

		auxlib.assert( self == result )

		auxlib.argcheck( auxlib.is_string( token ), 2, "(string expected)" )

		auxlib.argcheck( auxlib.is_function( proc ) or auxlib.is_nil( proc ), 3, "(function expected)" )

		metatable.Observer[ token ] = proc

		return self
	end

	function metatable.__index:get( options, payload, ackfn, timeout )

		return self:request( CoAP.REQUEST_GET, options, payload, ackfn, timeout )
	end

	function metatable.__index:post( options, payload, ackfn, timeout )

		return self:request( CoAP.REQUEST_POST, options, payload, ackfn, timeout )
	end

	function metatable.__index:put( options, payload, ackfn, timeout )

		return self:request( CoAP.REQUEST_PUT, options, payload, ackfn, timeout )
	end

	function metatable.__index:delete( options, payload, ackfn, timeout )

		return self:request( CoAP.REQUEST_DELETE, options, payload, ackfn, timeout )
	end

	function metatable.__index:bind_path( path, proc )

		return self:bind( path, function( self, code, token, options, payload )

			local session = { model = "TCP", code = code, token = token }

			session.async = function()

				session.async = nil
				session.reset = nil
			end

			session.reply = function( code, options, payload )

				self:response( code, token, options, payload )

				session.async = nil
				session.reply = nil
				session.reset = nil
			end

			session.reset = function( code, options, payload )

				self:response( code, token, options, payload )

				session.async = nil
				session.reply = nil
				session.reset = nil
			end

			auxlib.pcall( proc, self, session, options, payload )
		end )
	end

	setmetatable( result, metatable )

	return result
end

local function new_stm_server( object )

	local result = {}

	local metatable = { Object = object,

		__index = {},

		__metatable = "cannot get a protected metatable"
	}

	function object.on_accept( client )

		if not auxlib.pcall( result.on_accept, result, new_stm_client( client ) ) then

			client:close()
		end
	end

	function object.on_close()

		auxlib.pcall( result.on_close, result )

		metatable.Object.on_accept = nil
		metatable.Object.on_close  = nil
		metatable.Object.on_ready  = nil

		metatable.Object = nil
	end

	function object.on_destroy()

		auxlib.pcall( result.on_destroy, result )

		result = nil
	end

	function object.on_ready()

		auxlib.pcall( result.on_ready, result )
	end

	function metatable.__index:close()

		auxlib.assert( self == result )

		auxlib.assert( metatable.Object:close() )

		return self
	end

	setmetatable( result, metatable )

	return result
end

--------------------------------------------------------------------------------

function auxcoap.new_loc_client( path )

	local object = auxlib.assert( CoAP.new_loc_client() )

	auxlib.assert( object:connect( path ) )

	return new_stm_client( object )
end

function auxcoap.new_loc_server( path, backlog )

	local object = auxlib.assert( CoAP.new_loc_server() )

	auxlib.assert( object:listen( path, backlog ) )

	return new_stm_server( object )
end

function auxcoap.new_tcp_client( host, port )

	local object = auxlib.assert( CoAP.new_tcp_client() )

	auxlib.assert( object:connect( host, port ) )

	return new_stm_client( object )
end

function auxcoap.new_tcp_server( addr, backlog )

	local object = auxlib.assert( CoAP.new_tcp_server() )

	auxlib.assert( object:listen( addr, backlog ) )

	return new_stm_server( object )
end

--------------------------------------------------------------------------------

local function new_udp_client( handler, methods )

	local metatable = { Observer = {}, URITable = {},

		__index = {},

		__metatable = "cannot get a protected metatable"
	}

	function metatable.__index:answer( type, code, token, options, payload, ackfn, timeout )

		auxlib.assert( self == handler )

		methods.answer( type, code, token, options, payload, function( self, ... )

			ackfn( handler, ... )

		end, timeout )

		return self
	end

	function metatable.__index:answer_busy( ident )

		auxlib.assert( self == handler )

		methods.answer_busy( ident )

		return self
	end

	function metatable.__index:close()

		auxlib.assert( self == handler )

		methods.close( self )

		handler = nil
		methods = nil
	end

	function metatable.__index:request( type, code, options, payload, ackfn, timeout )

		auxlib.assert( self == handler )

		methods.request( type, code, options, payload, function( self, ... )

			ackfn( handler, ... )

		end, timeout )

		return self
	end

	function metatable.__index:response( type, code, ident, token, options, payload )

		auxlib.assert( self == handler )

		methods.response( type, code, ident, token, options, payload )

		return self
	end

	function metatable.__index:publish( token, index, payload, options, code, ackfn, timeout )

		auxlib.assert( self == handler )

		auxlib.argcheck( auxlib.is_integer( index ), 3, "(integer expected)" )

		if auxlib.is_none( code ) then

			code = CoAP.RESPONSE_CONTENT
		end

		auxlib.argcheck( code > 31, 6, "(invalid value)" )

		if auxlib.is_none( options ) then

			options = {}
		end

		options[ CoAP.OPTION_OBSERVE ] = index

		methods.answer( ( ( ackfn and CoAP.CON ) or CoAP.NON ), code, token, options, payload, function( self, ... )

			ackfn( handler, ... )

		end, timeout )

		return self
	end

	function metatable.__index:ping( ackfn, timeout )

		auxlib.assert( self == handler )

		methods.ping( ackfn, timeout )

		return self
	end

	function metatable.__index:get( options, payload, ackfn, timeout )

		return self:request( ( ( ackfn and CoAP.CON ) or CoAP.NON ), CoAP.REQUEST_GET, options, payload, ackfn, timeout )
	end

	function metatable.__index:post( options, payload, ackfn, timeout )

		return self:request( ( ( ackfn and CoAP.CON ) or CoAP.NON ), CoAP.REQUEST_POST, options, payload, ackfn, timeout )
	end

	function metatable.__index:put( options, payload, ackfn, timeout )

		return self:request( ( ( ackfn and CoAP.CON ) or CoAP.NON ), CoAP.REQUEST_PUT, options, payload, ackfn, timeout )
	end

	function metatable.__index:delete( options, payload, ackfn, timeout )

		return self:request( ( ( ackfn and CoAP.CON ) or CoAP.NON ), CoAP.REQUEST_DELETE, options, payload, ackfn, timeout )
	end

	function metatable.__index:bind( path, proc )

		auxlib.assert( self == handler )

		auxlib.argcheck( auxlib.is_string( path ), 2, "(string expected)" )

		auxlib.argcheck( auxlib.is_function( proc ) or auxlib.is_nil( proc ), 3, "(function expected)" )

		metatable.URITable[ hash_uri_path( path ) ] = proc

		return self
	end

	function metatable.__index:bind_path( path, proc )

		return self:bind( path, function( self, type, code, ident, token, options, payload )

			local session = { model = "UDP", type = type, code = code, token = token }

			function session.async()

				session.async = nil
				session.reset = nil

				if CoAP.CON == type then

					self:answer_busy( ident )

					session.reply = function( code, options, payload, ackfn, timeout )

						session.reply = nil

						self:answer( ( ( ackfn and CoAP.CON ) or CoAP.NON ), code, token, options, payload, ackfn, timeout )
					end
				end
			end

			function session.reply( code, options, payload )

				session.async = nil
				session.reply = nil
				session.reset = nil

				if CoAP.CON == type then

					self:response( CoAP.ACK, code, ident, token, options, payload )
				else

					self:answer( CoAP.NON, code, token, options, payload )
				end
			end

			function session.reset( code, options, payload )

				session.async = nil
				session.reply = nil
				session.reset = nil

				if CoAP.CON == type then

					self:response( CoAP.RST, code, ident, token, options, payload )
				else

					self:answer( CoAP.NON, code, token, options, payload )
				end
			end

			auxlib.pcall( proc, self, session, options, payload )
		end )
	end

	function metatable.__index:observe( token, proc )

		auxlib.assert( self == handler )

		auxlib.argcheck( auxlib.is_string( token ), 2, "(string expected)" )

		auxlib.argcheck( auxlib.is_function( proc ) or auxlib.is_nil( proc ), 3, "(function expected)" )

		metatable.Observer[ token ] = proc

		return self
	end

	function metatable.__index:publish_wrap( token )

		local ident = 1

		return function( payload, options, code, ackfn, timeout )

			local index = ident

			ident = ( ident + 1 ) % 0x1000000

			return self:publish( token, index, payload, options, code, ackfn, timeout )
		end
	end

	function handler:on_request( type, code, ident, token, options, payload )

		local proc = metatable.URITable[ hash_uri_path( options ) ]

		if proc then

			auxlib.pcall( proc, self, type, code, ident, token, options, payload )

		elseif auxcoap.is_ping_message( type, code, token ) then

			self:response( CoAP.RST, code, ident, token, nil, nil )
		else

			self:response( CoAP.RST, CoAP.RESPONSE_NOT_FOUND, ident, token, nil, nil )
		end
	end

	function handler:on_response( type, code, ident, token, options, payload )

		auxlib.pcall( metatable.Observer[ token ], handler, type, code, ident, token, options, payload )
	end

	setmetatable( handler, metatable )

	return handler
end

function auxcoap.new_udp_object( addr )

	local object = auxlib.assert( CoAP.new_udp_object() )

	auxlib.assert( object:create( addr ) )

	local result = {}

	local metatable = { Ident = 0, Object = object, Client = {},

		__index = {},

		__metatable = "cannot get a protected metatable"
	}

	metatable.Registry = auxtmr.create_timeout_container( 1000, 5000, function( value )

		auxlib.pcall( value.ackfn, result )
	end )

	function object.on_close()

		metatable.Registry.clear( true )

		metatable.Registry.close()

		auxlib.table_for_each( metatable.Client, function( k, v )

			auxlib.pcall( v.on_close, v )
		end )

		auxlib.pcall( result.on_close, result )

		metatable.Object.on_close    = nil
		metatable.Object.on_ready    = nil
		metatable.Object.on_request  = nil
		metatable.Object.on_response = nil

		metatable.Ident    = nil
		metatable.Object   = nil
		metatable.Client   = nil
		metatable.Registry = nil

		metatable = nil
	end

	function object.on_destroy()

		auxlib.pcall( result.on_destroy, result )

		result = nil
	end

	function object.on_ready()

		auxlib.pcall( result.on_ready, result )
	end

	function object.on_request( addr, type, code, ident, token, options, payload )

		if code > 31 then

			metatable:on_response( addr, type, code, ident, token, options, payload )
		else

			metatable:on_request( addr, type, code, ident, token, options, payload )
		end
	end

	function object.on_response( addr, type, code, ident, token, options, payload )

		metatable:on_response( addr, type, code, ident, token, options, payload )
	end

	function metatable:gen_ident()

		local ident = ( self.Ident + 1 ) % 65536

		self.Ident = ident

		return ident
	end

	function metatable:on_request( addr, type, code, ident, token, options, payload )

		local client = self.Client[ addr ]

		if nil == client then

			auxlib.pcall( result.on_accept, result, addr )

			client = self.Client[ addr ]
		end

		self.peername = addr

		if client then

			auxlib.pcall( client.on_request, client, type, code, ident, token, options, payload )
		else

			auxlib.pcall( result.on_request, result, addr, type, code, ident, token, options, payload )
		end

		self.peername = nil
	end

	function metatable:on_response( addr, type, code, ident, token, options, payload )

		local client = self.Client[ addr ]

		self.peername = addr

		if CoAP.ACK == type then

			local item = self.Registry.remove( ident )

			if nil ~= item then

				if CoAP.RESPONSE_EMPTY == code and nil == token then

					if not self.Registry.insert_raw( item.token, { ackfn = item.ackfn }, item.clock ) then

						auxlib.pcall( item.ackfn, ( client or result ), type, code, token, options, payload )
					end

				elseif token == item.token then

					auxlib.pcall( item.ackfn, ( client or result ), type, code, token, options, payload )
				end

			elseif client then

				auxlib.pcall( client.on_response, client, type, code, ident, token, options, payload )
			else

				auxlib.pcall( result.on_response, result, addr, type, code, ident, token, options, payload )
			end

		elseif CoAP.RST == type then

			local item = ( self.Registry.remove( ident ) or self.Registry.remove( token ) )

			if nil ~= item and token == item.token then

				auxlib.pcall( item.ackfn, ( client or result ), type, code, token, options, payload )

			elseif client then

				auxlib.pcall( client.on_response, client, type, code, ident, token, options, payload )
			else

				auxlib.pcall( result.on_response, result, addr, type, code, ident, token, options, payload )
			end
		else

			local item = self.Registry.remove( token )

			if nil ~= item then

				if CoAP.CON == type then

					self.Object:send( addr, CoAP.ACK, CoAP.RESPONSE_EMPTY, ident, nil, nil, nil )
				end

				auxlib.pcall( item.ackfn, ( client or result ), type, code, token, options, payload )

			elseif client then

				auxlib.pcall( client.on_response, client, type, code, ident, token, options, payload )
			else

				auxlib.pcall( result.on_response, result, addr, type, code, ident, token, options, payload )
			end
		end

		self.peername = nil
	end

	function metatable.__index:broadcast( port, code, token, options, payload )

		auxlib.assert( self == result )

		auxlib.assert( metatable.Object:broadcast( port, CoAP.NON, code, metatable:gen_ident(), token, options, payload ) )

		return self
	end

	function metatable.__index:close()

		auxlib.assert( self == result )

		auxlib.assert( metatable.Object:close() )

		return self
	end

	function metatable.__index:connect( addr, handler )

		auxlib.assert( self == result )

		auxlib.argcheck( nil == metatable.Client[ addr ], 2, "(object already exists)" )

		auxlib.argcheck( auxlib.is_table( handler ), 3, "(table expected)" )

		local methods = {

			answer = function( type, code, token, options, payload, ackfn, timeout )

				self:answer( addr, type, code, token, options, payload, ackfn, timeout )
			end,

			answer_busy = function( ident )

				self:answer_busy( addr, ident )
			end,

			close = function( self )

				metatable.Client[ addr ] = nil

				auxlib.pcall( self.on_close, self )
			end,

			ping = function( ackfn, timeout )

				self:ping( addr, ackfn, timeout )
			end,

			request = function( type, code, options, payload, ackfn, timeout )

				self:request( addr, type, code, options, payload, ackfn, timeout )
			end,

			response = function( type, code, ident, token, options, payload )

				self:response( addr, type, code, ident, token, options, payload )
			end
		}

		metatable.Client[ addr ] = new_udp_client( handler, methods )

		auxlib.pcall( handler.on_ready, handler )
	end

	function metatable.__index:multicast( addr, code, token, options, payload )

		auxlib.assert( self == result )

		auxlib.assert( metatable.Object:send( addr, CoAP.NON, code, metatable:gen_ident(), token, options, payload ) )

		return self
	end

	function metatable.__index:multicast_add( addr, loop )

		auxlib.assert( self == result )

		auxlib.assert( metatable.Object:multicast_add( addr, loop ) )

		return self
	end

	function metatable.__index:set_broadcast( value )

		auxlib.assert( self == result )

		auxlib.assert( metatable.Object:set_broadcast( value ) )

		return self
	end

	function metatable.__index:answer( addr, type, code, token, options, payload, ackfn, timeout )

		auxlib.assert( self == result )

		auxlib.argcheck( CoAP.CON == type or CoAP.NON == type, 3, "(CON or NON expected)" )

		auxlib.argcheck( code > 31, 4, "(32 to 255 expected)" )

		local ident = metatable:gen_ident()

		auxlib.assert( metatable.Object:send( addr, type, code, ident, token, options, payload ) )

		if CoAP.CON == type and ackfn then

			auxlib.assert( metatable.Registry.insert( ident, { ackfn = ackfn }, timeout ), "unable to register session" )
		end

		return self
	end

	function metatable.__index:answer_busy( addr, ident )

		auxlib.assert( self == result )

		auxlib.assert( metatable.Object:send( addr, CoAP.ACK, CoAP.RESPONSE_EMPTY, ident, nil, nil, nil ) )

		return self
	end

	function metatable.__index:getclient( addr )

		auxlib.assert( self == result )

		return metatable.Client[ addr ]
	end

	function metatable.__index:getpeername()

		auxlib.assert( self == result )

		return metatable.peername
	end

	function metatable.__index:ping( addr, ackfn, timeout )

		auxlib.assert( self == result )

		local ident = metatable:gen_ident()

		auxlib.assert( metatable.Object:send( addr, CoAP.CON, CoAP.RESPONSE_EMPTY, ident, nil, nil, nil ) )

		if ackfn then

			auxlib.assert( metatable.Registry.insert( ident, { ackfn = ackfn }, timeout ), "unable to register session" )
		end
	end

	function metatable.__index:pong( addr, ident )

		auxlib.assert( self == result )

		auxlib.assert( metatable.Object:send( addr, CoAP.RST, CoAP.RESPONSE_EMPTY, ident, nil, nil, nil ) )

		return self
	end

	function metatable.__index:request( addr, type, code, options, payload, ackfn, timeout )

		auxlib.assert( self == result )

		auxlib.argcheck( CoAP.CON == type or CoAP.NON == type, 3, "(CON or NON expected)" )

		local ident = metatable:gen_ident()

		local token = gen_token( addr, ident )

		auxlib.assert( metatable.Object:send( addr, type, code, ident, token, options, payload ) )

		if ackfn then

			local clock = os.get_tick_count() + auxlib.if_binary( timeout, 5000, auxlib.is_integer )

			if CoAP.CON == type then

				auxlib.assert( metatable.Registry.insert_raw( ident, { ackfn = ackfn, token = token, clock = clock }, clock ), "unable to register session" )
			else

				auxlib.assert( metatable.Registry.insert_raw( token, { ackfn = ackfn, token = token }, clock ), "unable to register session" )
			end
		end

		return self
	end

	function metatable.__index:response( addr, type, code, ident, token, options, payload )

		auxlib.assert( self == result )

		auxlib.argcheck( CoAP.ACK == type or CoAP.RST == type, 3, "(ACK or RST expected)" )

		auxlib.assert( metatable.Object:send( addr, type, code, ident, token, options, payload ) )

		return self
	end

	setmetatable( result, metatable )

	return result
end

--------------------------------------------------------------------------------

function auxcoap.new_udp_client( addr, result )

	local object = nil

	local ipv4 = string.match( addr, "(([%d]+).([%d]+).([%d]+).([%d]+)):([%d]+)" )

	if nil ~= ipv4 then

		object = auxcoap.new_udp_object( "IPv4" )
	else

		object = auxcoap.new_udp_object( "IPv6" )
	end

	result = result or {}

	function object:on_ready()

		self.on_ready = nil

		if not auxlib.pcall( self.connect, self, addr, result ) then

			self:close()
		end
	end

	function result:close()

		object:close()

		object = nil
	end

	return result
end

--------------------------------------------------------------------------------

local type2desc = {

	[ CoAP.CON ] = "CON",
	[ CoAP.NON ] = "NON",
	[ CoAP.ACK ] = "ACK",
	[ CoAP.RST ] = "RST"
}

function auxcoap.type2desc( v )

	return type2desc[ v ]
end

--------------------------------------------------------------------------------

local code2desc = {

	[ CoAP.REQUEST_EMPTY  ] = "Empty",
	[ CoAP.REQUEST_GET    ] = "GET",
	[ CoAP.REQUEST_POST   ] = "POST",
	[ CoAP.REQUEST_PUT    ] = "PUT",
	[ CoAP.REQUEST_DELETE ] = "DELETE",

	[ CoAP.RESPONSE_CREATED                    ] = "Created",
	[ CoAP.RESPONSE_DELETED                    ] = "Deleted",
	[ CoAP.RESPONSE_VALID                      ] = "Valid",
	[ CoAP.RESPONSE_CHANGED                    ] = "Changed",
	[ CoAP.RESPONSE_CONTENT                    ] = "Content",
	[ CoAP.RESPONSE_CONTINUE                   ] = "Continue",
	[ CoAP.RESPONSE_BAD_REQUEST                ] = "Bad Request",
	[ CoAP.RESPONSE_UNAUTHORIZED               ] = "Unauthorized",
	[ CoAP.RESPONSE_BAD_OPTION                 ] = "Bad Option",
	[ CoAP.RESPONSE_FORBIDDEN                  ] = "Forbidden",
	[ CoAP.RESPONSE_NOT_FOUND                  ] = "Not Found",
	[ CoAP.RESPONSE_METHOD_NOT_ALLOWED         ] = "Method Not Allowed",
	[ CoAP.RESPONSE_NOT_ACCEPTABLE             ] = "Not Acceptable",
	[ CoAP.RESPONSE_REQUEST_ENTITY_INCOMPLETE  ] = "Request Entity Incomplete",
	[ CoAP.RESPONSE_PRECONDITION_FAILED        ] = "Precondition Failed",
	[ CoAP.RESPONSE_REQUEST_ENTITY_TOO_LARGE   ] = "Request Entity Too Large",
	[ CoAP.RESPONSE_UNSUPPORTED_CONTENT_FORMAT ] = "Unsupported Content-Format",
	[ CoAP.RESPONSE_INTERNAL_SERVER_ERROR      ] = "Internal Server Error",
	[ CoAP.RESPONSE_NOT_IMPLEMENTED            ] = "Not Implemented",
	[ CoAP.RESPONSE_BAD_GATEWAY                ] = "Bad Gateway",
	[ CoAP.RESPONSE_SERVICE_UNAVAILABLE        ] = "Service Unavailable",
	[ CoAP.RESPONSE_GATEWAY_TIMEOUT            ] = "Gateway Timeout",
	[ CoAP.RESPONSE_PROXYING_NOT_SUPPORTED     ] = "Proxying Not Supported"
}

function auxcoap.code2desc( v )

	return code2desc[ v ]
end

function auxcoap.code2numstr( v )

	return string.format( "%d.%02d", v >> 5, v & 0x1f )
end

--------------------------------------------------------------------------------

function auxcoap.block_wise_get( get, uri, num, cap, proc )

	local block = { num = 0, more = false, szx = 6 }

	if auxlib.is_integer( num ) then

		block.num = ( ( num < 0 and 0 ) or num )
	end

	if auxlib.is_integer( cap ) then

		local range = { 16, 32, 64, 128, 256, 512, 1024 }

		for i = 1, #range do

			if cap <= range[ i ] then

				block.szx = i - 1

				break
			end
		end
	end

	local function ackfn( self, type, code, token, options, payload )

		if CoAP.ACK == type then

			block = CoAP.get_option_block( options[ CoAP.OPTION_BLOCK2 ] )

			local b, r = auxlib.pcall( proc, ( block and block.num ) or 0, ( block and block.more ) or false, payload, options[ CoAP.OPTION_SIZE2 ] )

			if b and r and block and block.more then

				block.num = block.num + 1

				block.more = false

				get( auxcoap.genopts( "Uri-Path", uri, "Block2", block ), nil, ackfn )
			end
		else

			local b, r = auxlib.pcall( proc )

			if b and r and block and false == block.more then

				get( auxcoap.genopts( "Uri-Path", uri, "Block2", block ), nil, ackfn )
			end
		end
	end

	get( auxcoap.genopts( "Uri-Path", uri, "Block2", block ), nil, ackfn )
end

--------------------------------------------------------------------------------

function auxcoap.new_data_block_wise( data, response )

	local size = #data

	return function( addr, ident, token, options )

		local block = CoAP.get_option_block( options[ CoAP.OPTION_BLOCK2 ] ) or { num = 0, more = false, szx = 6 }

		local bytes = 2 ^ ( block.szx + 4 )

		local where = block.num * bytes

		if where < size then

			local payload = data:sub( where + 1, where + bytes )

			block.more = ( where + bytes ) < size

			response( addr, CoAP.ACK, CoAP.RESPONSE_CONTENT, ident, token, auxcoap.genopts( "Block2", block, "Size2", size ), payload )
		else

			response( addr, CoAP.RST, CoAP.RESPONSE_BAD_OPTION, ident, token )
		end
	end
end

function auxcoap.new_file_block_wise( filename, response )

	local file = io.open( filename, "rb" )

	local data = file:read( 'a' )

	file:close()

	file = nil

	return auxcoap.new_data_block_wise( data, response )
end

--------------------------------------------------------------------------------

return auxcoap
